using System;
using System.Collections.Generic;
using System.Text;
using slcl.Packets;
using SimpleMessageQueue;

namespace slcl
{
    /// <summary>
    /// a script dialog event. 
    /// </summary>
    public class ScriptDialog
    {
        public Client Client;
        public string Message;
        public string ObjectName;
        public LLUUID ImageID;
        public LLUUID ObjectID;
        public string FirstName;
        public string LastName;
        public int Channel;
        public List<string> Buttons;

        public ScriptDialog()
        {
        }

        public ScriptDialog(Client client, string msg, string name, LLUUID iid, LLUUID oid, string fname, string lname, int chan, List<string> buttons)
        {
            Client = client;
            Message = msg;
            ObjectName = name;
            ImageID = iid;
            ObjectID = oid;
            FirstName = fname;
            LastName = lname;
            Channel = chan;
            Buttons = buttons;
        }

        public bool ClickButton(int idx)
        {
            Client.Log("Clicking a button on ScriptDialog is not supported yet.", LogLevel.Warning);
            return false;
        }
        public bool ClickButton(string Text)
        {
            Client.Log("Clicking a button on ScriptDialog is not supported yet.", LogLevel.Warning);
            return false;
        }
    }

    /// <summary>
    /// An instant message event.
    /// </summary>
    public class InstantMessage
    {
        public Client Client;
        public LLUUID FromAgentID;
        public string FromAgentName;
        public LLUUID ToAgentID;
        public uint ParentEstateID;
        public LLUUID RegionID;
        public LLVector3 Position;
        public CommunicationManager.InstantMessageDialog Dialog;
        public bool IsGroupIM;
        public LLUUID SessionID;
        public DateTime Timestamp;
        public string Message;
        public CommunicationManager.InstantMessageOnline Offline;
        public byte[] BinaryBucket;
        public InstantMessage()
        {

        }
        public InstantMessage(Client client, LLUUID fromID, string fromName, LLUUID toID, uint estateID, LLUUID regionID, 
            LLVector3 pos, CommunicationManager.InstantMessageDialog dialog, bool fromGroup, LLUUID sesID
            , DateTime timestamp, string msg, CommunicationManager.InstantMessageOnline offline, byte[] binary)
        {
            Client = client;
            FromAgentID = fromID;
            FromAgentName = fromName;
            ToAgentID = toID;
            ParentEstateID = estateID;
            RegionID = regionID;
            Position = pos;
            Dialog = dialog;
            IsGroupIM = fromGroup;
            SessionID = sesID;
            Timestamp = timestamp;
            Message = msg;
            Offline = offline;
            BinaryBucket = binary;
        }

        public bool Reply(string msg)
        {
            Client.Log("Reply is not supported yet.", LogLevel.Warning);
            return false;
        }
    }

    /// <summary>
    /// a script question.
    /// </summary>
    public class ScriptQuestion
    {
        public LLUUID TaskID;
        public LLUUID ItemID;
        public string Name;
        public string Owner;
        public CommunicationManager.ScriptPermission Questions;
        public Client Client;

        public ScriptQuestion()
        {

        }

        public ScriptQuestion(Client client, LLUUID taskid, LLUUID itemid, string name, string owner
            , CommunicationManager.ScriptPermission questions)
        {
            Client = client;
            TaskID = taskid;
            ItemID = itemid;
            Name = name;
            Owner = owner;
            Questions = questions;
        }

        public bool Reply()
        {
            Client.Log("Reply via ScriptQuestionCarrier isn't supported yet.", LogLevel.Warning);
            return false;
        }
    }

    public class Chat
    {
        public Client Client;
        public string Message;
        public CommunicationManager.ChatAudibleLevel AudibleLevel;
        public CommunicationManager.ChatType Type;
        public CommunicationManager.ChatSourceType SourceType;
        public string FromName;
        public LLUUID SourceID;
        public LLUUID OwnerID;
        public LLVector3 Position;

        public Chat()
        {
        }

        public Chat(Client client, string msg, CommunicationManager.ChatAudibleLevel lvl
            , CommunicationManager.ChatType type, CommunicationManager.ChatSourceType srcType
            , string from, LLUUID sourceid, LLUUID ownerid, LLVector3 pos)
        {
            Client = client;
            Message = msg;
            AudibleLevel = lvl;
            Type = type;
            SourceType = srcType;
            FromName = from;
            SourceID = sourceid;
            OwnerID = ownerid;
            Position = pos;
        }
    }

    /// <summary>
    /// Manages the flow of communications events and objects.
    /// </summary>
    public class CommunicationManager
    {
        Client Client;
        Grid Grid;

        MessageQueue MQueue;



        #region enums

        public enum Event
        {
            InstantMessage,
            Chat,
            ScriptDialog,
            ScriptQuestion,
            Shutdown            
        }

        public enum Command
        {
            Say,
            InstantMessage,
            Shutdown
        }

        /// <summary>
        /// 
        /// </summary>
        [Flags]
        public enum ScriptPermission : int
        {
            /// <summary>Placeholder for empty values, shouldn't ever see this</summary>
            None = 0,
            /// <summary>Script wants to take money from you</summary>
            Debit = 1 << 1,
            /// <summary></summary>
            TakeControls = 1 << 2,
            /// <summary></summary>
            RemapControls = 1 << 3,
            /// <summary>Script wants to trigger avatar animations</summary>
            TriggerAnimation = 1 << 4,
            /// <summary></summary>
            Attach = 1 << 5,
            /// <summary></summary>
            ReleaseOwnership = 1 << 6,
            /// <summary></summary>
            ChangeLinks = 1 << 7,
            /// <summary></summary>
            ChangeJoints = 1 << 8,
            /// <summary></summary>
            ChangePermissions = 1 << 9,
            /// <summary></summary>
            TrackCamera = 1 << 10,
            /// <summary>Script wants to control your camera</summary>
            ControlCamera = 1 << 11
        }


        /// <summary>
        /// Special commands used in Instant Messages
        /// </summary>
        public enum InstantMessageDialog : byte
        {
            /// <summary>Indicates a regular IM from another agent</summary>
            MessageFromAgent = 0,
            /// <summary>Simple notification box with an OK button</summary>
            MessageBox = 1,
            /// <summary>Used to show a countdown notification with an OK
            /// button, deprecated now</summary>
            [Obsolete]
            MessageBoxCountdown = 2,
            /// <summary>You've been invited to join a group.</summary>
            GroupInvitation = 3,
            /// <summary>Inventory offer</summary>
            InventoryOffered = 4,
            /// <summary>Accepted inventory offer</summary>
            InventoryAccepted = 5,
            /// <summary>Declined inventory offer</summary>
            InventoryDeclined = 6,
            /// <summary>Group vote</summary>
            GroupVote = 7,
            /// <summary>A message to everyone in the agent's group, no longer
            /// used</summary>
            [Obsolete]
            DeprecatedGroupMessage = 8,
            /// <summary>An object is offering its inventory</summary>
            TaskInventoryOffered = 9,
            /// <summary>Accept an inventory offer from an object</summary>
            TaskInventoryAccepted = 10,
            /// <summary>Decline an inventory offer from an object</summary>
            TaskInventoryDeclined = 11,
            /// <summary>Unknown</summary>
            NewUserDefault = 12,
            /// <summary>Start a session, or add users to a session</summary>
            SessionAdd = 13,
            /// <summary>Start a session, but don't prune offline users</summary>
            SessionOfflineAdd = 14,
            /// <summary>Start a session with your group</summary>
            SessionGroupStart = 15,
            /// <summary>Start a session without a calling card (finder or objects)</summary>
            SessionCardlessStart = 16,
            /// <summary>Send a message to a session</summary>
            SessionSend = 17,
            /// <summary>Leave a session</summary>
            SessionDrop = 18,
            /// <summary>Indicates that the IM is from an object</summary>
            MessageFromObject = 19,
            /// <summary>sent an IM to a busy user, this is the auto response</summary>
            BusyAutoResponse = 20,
            /// <summary>Shows the message in the console and chat history</summary>
            ConsoleAndChatHistory = 21,
            /// <summary>IM Types used for luring your friends</summary>
            RequestTeleport = 22,
            /// <summary>Response sent to the agent which inititiated a teleport invitation</summary>
            AcceptTeleport = 23,
            /// <summary>Response sent to the agent which inititiated a teleport invitation</summary>
            DenyTeleport = 24,
            /// <summary>Only useful if you have Linden permissions</summary>
            GodLikeRequestTeleport = 25,
            /// <summary>A placeholder type for future expansion, currently not
            /// used</summary>
            CurrentlyUnused = 26,
            /// <summary>Notification of a new group election, this is 
            /// deprecated</summary>
            [Obsolete]
            DeprecatedGroupElection = 27,
            /// <summary>IM to tell the user to go to an URL</summary>
            GotoUrl = 28,
            /// <summary>IM for help</summary>
            Session911Start = 29,
            /// <summary>IM sent automatically on call for help, sends a lure 
            /// to each Helper reached</summary>
            Lure911 = 30,
            /// <summary>Like an IM but won't go to email</summary>
            FromTaskAsAlert = 31,
            /// <summary>IM from a group officer to all group members</summary>
            GroupNotice = 32,
            /// <summary>Unknown</summary>
            GroupNoticeInventoryAccepted = 33,
            /// <summary>Unknown</summary>
            GroupNoticeInventoryDeclined = 34,
            /// <summary>Accept a group invitation</summary>
            GroupInvitationAccept = 35,
            /// <summary>Decline a group invitation</summary>
            GroupInvitationDecline = 36,
            /// <summary>Unknown</summary>
            GroupNoticeRequested = 37,
            /// <summary>An avatar is offering you friendship</summary>
            FriendshipOffered = 38,
            /// <summary>An avatar has accepted your friendship offer</summary>
            FriendshipAccepted = 39,
            /// <summary>An avatar has declined your friendship offer</summary>
            FriendshipDeclined = 40,
            /// <summary>Indicates that a user has started typing</summary>
            StartTyping = 41,
            /// <summary>Indicates that a user has stopped typing</summary>
            StopTyping = 42
        }

        /// <summary>
        /// Flag in Instant Messages, whether the IM should be delivered to
        /// offline avatars as well
        /// </summary>
        public enum InstantMessageOnline
        {
            /// <summary>Only deliver to online avatars</summary>
            Online = 0,
            /// <summary>If the avatar is offline the message will be held until
            /// they login next, and possibly forwarded to their e-mail account</summary>
            Offline = 1
        }

        /// <summary>
        /// Conversion type to denote Chat Packet types in an easier-to-understand format
        /// </summary>
        public enum ChatType : byte
        {
            /// <summary>Whisper (5m radius)</summary>
            Whisper = 0,
            /// <summary>Normal chat (10/20m radius), what the official viewer typically sends</summary>
            Normal = 1,
            /// <summary>Shouting! (100m radius)</summary>
            Shout = 2,
            /// <summary>Say chat (10/20m radius) - The official viewer will 
            /// print "[4:15] You say, hey" instead of "[4:15] You: hey"</summary>
            [Obsolete]
            Say = 3,
            /// <summary>Event message when an Avatar has begun to type</summary>
            StartTyping = 4,
            /// <summary>Event message when an Avatar has stopped typing</summary>
            StopTyping = 5,
            /// <summary>Unknown</summary>
            Debug = 6
        }

        /// <summary>
        /// Identifies the source of a chat message
        /// </summary>
        public enum ChatSourceType : byte
        {
            /// <summary>Chat from the grid or simulator</summary>
            System = 0,
            /// <summary>Chat from another avatar</summary>
            Agent = 1,
            /// <summary>Chat from an object</summary>
            Object = 2
        }

        /// <summary>
        /// 
        /// </summary>
        public enum ChatAudibleLevel : sbyte
        {
            /// <summary></summary>
            Not = -1,
            /// <summary></summary>
            Barely = 0,
            /// <summary></summary>
            Fully = 1
        }


        #endregion

        #region Constructor/destructor
		
        public CommunicationManager(Client client)
        {
            Client = client;
            Grid = client.Grid;
            MQueue = client.MQueue;

            // Instant Message callback
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.ImprovedInstantMessage, new CommandMessage.AckCallback(InstantMessageHandler)), Grid);

            // Chat callback
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.ChatFromSimulator, new CommandMessage.AckCallback(ChatHandler)), Grid);

            // Script dialog callback
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.ScriptDialog, new CommandMessage.AckCallback(ScriptDialogHandler)), Grid);

            // Script question callback
            MQueue.Send(new CommandMessage(Grid.Command.Register, PacketType.ScriptQuestion, new CommandMessage.AckCallback(ScriptQuestionHandler)), Grid);
        }


	#endregion        #region Commands

        #region client actions

        /// <summary>
        /// Send an Instant Message
        /// </summary>
        /// <param name="target">Target of the Instant Message</param>
        /// <param name="message">Text message being sent</param>
        public void InstantMessage(LLUUID target, string message)
        {
            InstantMessage(Client.ToString(), target, message, LLUUID.Random(),
                InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, Client.Movement.Position,
                LLUUID.Zero, new byte[0]);
        }

        /// <summary>
        /// Send an Instant Message
        /// </summary>
        /// <param name="target">Target of the Instant Message</param>
        /// <param name="message">Text message being sent</param>
        /// <param name="imSessionID">IM session ID (to differentiate between IM windows)</param>
        public void InstantMessage(LLUUID target, string message, LLUUID imSessionID)
        {
            InstantMessage(Client.ToString(), target, message, imSessionID,
                InstantMessageDialog.MessageFromAgent, InstantMessageOnline.Offline, Client.Movement.Position,
                LLUUID.Zero, new byte[0]);
        }

        /// <summary>
        /// Send an Instant Message
        /// </summary>
        /// <param name="fromName">The name this IM will show up as being from</param>
        /// <param name="target">Key of Avatar</param>
        /// <param name="message">Text message being sent</param>
        /// <param name="imSessionID">IM session ID (to differentiate between IM windows)</param>
        /// <param name="conferenceIDs"></param>
        public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID,
            LLUUID[] conferenceIDs)
        {
            byte[] binaryBucket;

            if (conferenceIDs != null && conferenceIDs.Length > 0)
            {
                binaryBucket = new byte[16 * conferenceIDs.Length];
                for (int i = 0; i < conferenceIDs.Length; ++i)
                    Array.Copy(conferenceIDs[i].Data, 0, binaryBucket, i * 16, 16);
            }
            else
            {
                binaryBucket = new byte[0];
            }

            InstantMessage(fromName, target, message, imSessionID, InstantMessageDialog.MessageFromAgent,
                InstantMessageOnline.Offline, LLVector3.Zero, LLUUID.Zero, binaryBucket);
        }

        /// <summary>
        /// Send an Instant Message
        /// </summary>
        /// <param name="fromName">The name this IM will show up as being from</param>
        /// <param name="target">Key of Avatar</param>
        /// <param name="message">Text message being sent</param>
        /// <param name="imSessionID">IM session ID (to differentiate between IM windows)</param>
        /// <param name="dialog">Type of instant message to send</param>
        /// <param name="offline">Whether to IM offline avatars as well</param>
        /// <param name="position"></param>
        /// <param name="regionID"></param>
        /// <param name="binaryBucket">Packed binary data that is specific to
        /// the dialog type</param>
        public void InstantMessage(string fromName, LLUUID target, string message, LLUUID imSessionID,
            InstantMessageDialog dialog, InstantMessageOnline offline, LLVector3 position, LLUUID regionID,
            byte[] binaryBucket)
        {
            ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();

            im.AgentData.AgentID = Grid.AgentID;
            im.AgentData.SessionID = Grid.SessionID;

            im.MessageBlock.Dialog = (byte)dialog;
            im.MessageBlock.FromAgentName = Helpers.StringToField(fromName);
            im.MessageBlock.FromGroup = false;
            im.MessageBlock.ID = imSessionID;
            im.MessageBlock.Message = Helpers.StringToField(message);
            im.MessageBlock.Offline = (byte)offline;
            im.MessageBlock.ToAgentID = target;

            if (binaryBucket != null)
                im.MessageBlock.BinaryBucket = binaryBucket;
            else
                im.MessageBlock.BinaryBucket = new byte[0];

            // These fields are mandatory, even if we don't have valid values for them
            im.MessageBlock.Position = LLVector3.Zero;
            //TODO: Allow region id to be correctly set by caller or fetched from Client.*
            im.MessageBlock.RegionID = regionID;

            // Send the message
            Grid.SendPacket(im);
        }

        /// <summary>
        /// Send an Instant Message to a group
        /// </summary>
        /// <param name="groupUUID">Key of Group</param>
        /// <param name="message">Text Message being sent.</param>
        public void InstantMessageGroup(LLUUID groupUUID, string message)
        {
            InstantMessageGroup(Client.ToString(), groupUUID, message);
        }

        /// <summary>
        /// Send an Instant Message to a group
        /// </summary>
        /// <param name="fromName">The name this IM will show up as being from</param>
        /// <param name="groupUUID">Key of the group</param>
        /// <param name="message">Text message being sent</param>
        /// <remarks>This does not appear to function with groups the agent is not in</remarks>
        public void InstantMessageGroup(string fromName, LLUUID groupUUID, string message)
        {
            ImprovedInstantMessagePacket im = new ImprovedInstantMessagePacket();

            im.AgentData.AgentID = Grid.AgentID;
            im.AgentData.SessionID = Grid.SessionID;
            im.MessageBlock.Dialog = (byte)InstantMessageDialog.SessionSend;
            im.MessageBlock.FromAgentName = Helpers.StringToField(fromName);
            im.MessageBlock.FromGroup = false;
            im.MessageBlock.Message = Helpers.StringToField(message);
            im.MessageBlock.Offline = 0;
            im.MessageBlock.ID = groupUUID;
            im.MessageBlock.ToAgentID = groupUUID;
            im.MessageBlock.BinaryBucket = new byte[0];
            im.MessageBlock.Position = LLVector3.Zero;
            im.MessageBlock.RegionID = LLUUID.Zero;

            // Send the message
            Grid.SendPacket(im);
        }

        /// <summary>
        /// Send a chat message
        /// </summary>
        /// <param name="message">The Message you're sending out.</param>
        /// <param name="channel">Channel number (0 would be default 'Say' message, other numbers 
        /// denote the equivalent of /# in normal client).</param>
        /// <param name="type">Chat Type, see above.</param>
        public void Chat(string message, int channel, ChatType type)
        {
            ChatFromViewerPacket chat = new ChatFromViewerPacket();
            chat.AgentData.AgentID = Client.ID;
            chat.AgentData.SessionID = Grid.SessionID;
            chat.ChatData.Channel = channel;
            chat.ChatData.Message = Helpers.StringToField(message);
            chat.ChatData.Type = (byte)type;

            Grid.SendPacket(chat);
        }

        /// <summary>
        /// Request the list of muted things for this avatar
        /// </summary>
        public void RequestMuteList()
        {
            MuteListRequestPacket mute = new MuteListRequestPacket();
            mute.AgentData.AgentID = Grid.AgentID;
            mute.AgentData.SessionID = Grid.SessionID;
            mute.MuteData.MuteCRC = 0;

            Grid.SendPacket(mute);
        }


        #endregion

        #region Packet Handlers
        /// <summary>
        /// Take an incoming ImprovedInstantMessage packet, auto-parse, and if
        /// OnInstantMessage is defined call that with the appropriate arguments
        /// </summary>
        /// <param name="packet">Incoming ImprovedInstantMessagePacket</param>
        /// <param name="simulator">Unused</param>
        private void InstantMessageHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            Simulator simulator = carrier.sim;
            if (carrier.packet.Type == PacketType.ImprovedInstantMessage)
            {
                ImprovedInstantMessagePacket im = (ImprovedInstantMessagePacket)carrier.packet;

                InstantMessage IM = new InstantMessage(Client
                                        , im.AgentData.AgentID
                                        , Helpers.FieldToUTF8String(im.MessageBlock.FromAgentName)
                                        , im.MessageBlock.ToAgentID
                                        , im.MessageBlock.ParentEstateID
                                        , im.MessageBlock.RegionID
                                        , im.MessageBlock.Position
                                        , (InstantMessageDialog)im.MessageBlock.Dialog
                                        , im.MessageBlock.FromGroup
                                        , im.MessageBlock.ID
                                        , new DateTime(im.MessageBlock.Timestamp)
                                        , Helpers.FieldToUTF8String(im.MessageBlock.Message)
                                        , (InstantMessageOnline)im.MessageBlock.Offline
                                        , im.MessageBlock.BinaryBucket
                                        );
                MQueue.MatchRegistered(Event.InstantMessage, AckMessage.StatusType.Completed, IM);
            }
        }    

        /// <summary>
        /// Take an incoming Chat packet, auto-parse, and if OnChat is defined call 
        ///   that with the appropriate arguments.
        /// </summary>
        /// <param name="packet">Incoming ChatFromSimulatorPacket</param>
        /// <param name="simulator">Unused</param>
        private void ChatHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            Simulator simulator = carrier.sim;
            ChatFromSimulatorPacket chat = (ChatFromSimulatorPacket)carrier.packet;

            Chat Chat = new Chat(Client
                , Helpers.FieldToUTF8String(chat.ChatData.Message)
                , (ChatAudibleLevel)chat.ChatData.Audible
                , (ChatType)chat.ChatData.ChatType
                , (ChatSourceType)chat.ChatData.SourceType
                , Helpers.FieldToUTF8String(chat.ChatData.FromName)
                , chat.ChatData.SourceID
                , chat.ChatData.OwnerID
                , chat.ChatData.Position
                );
            MQueue.MatchRegistered(Event.Chat, AckMessage.StatusType.Completed, Chat);
        }        

        /// <summary>
        /// Used for parsing llDialog's
        /// </summary>
        /// <param name="packet">Incoming ScriptDialog packet</param>
        /// <param name="simulator">Unused</param>
        private void ScriptDialogHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            Simulator simulator = carrier.sim;
            ScriptDialogPacket dialog = (ScriptDialogPacket)carrier.packet;
            List<string> buttons = new List<string>();

            foreach (ScriptDialogPacket.ButtonsBlock button in dialog.Buttons)
            {
                buttons.Add(Helpers.FieldToUTF8String(button.ButtonLabel));
            }

            ScriptDialog ScriptDialog = new ScriptDialog(Client
                , Helpers.FieldToUTF8String(dialog.Data.Message),
                Helpers.FieldToUTF8String(dialog.Data.ObjectName),
                dialog.Data.ImageID,
                dialog.Data.ObjectID,
                Helpers.FieldToUTF8String(dialog.Data.FirstName),
                Helpers.FieldToUTF8String(dialog.Data.LastName),
                dialog.Data.ChatChannel,
                buttons);
            MQueue.MatchRegistered(Event.ScriptDialog, AckMessage.StatusType.Completed, ScriptDialog);
        }
        /// <summary>
        /// Used for parsing llRequestPermissions dialogs
        /// </summary>
        /// <param name="packet">Incoming ScriptDialog packet</param>
        /// <param name="simulator">Unused</param>
        private void ScriptQuestionHandler(AckMessage Ack)
        {
            PacketCarrier carrier = (PacketCarrier)Ack.Data;
            Simulator simulator = carrier.sim;
            ScriptQuestionPacket question = (ScriptQuestionPacket)carrier.packet;

            try
            {
                ScriptQuestion Question = new ScriptQuestion(Client
                    , question.Data.TaskID
                    , question.Data.ItemID
                    , Helpers.FieldToUTF8String(question.Data.ObjectName)
                    , Helpers.FieldToUTF8String(question.Data.ObjectOwner)
                    , (ScriptPermission)question.Data.Questions);
                MQueue.MatchRegistered(Event.ScriptQuestion, AckMessage.StatusType.Completed, Question);
            }
            catch (Exception e) { Client.Log(e.ToString(), LogLevel.Error); }
        }
        #endregion

    }
}
